package org.ouwh.fw.threadpool;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class PushingThreadPool implements IThreadPool {
	private static Log log = LogFactory.getLog(PushingThreadPool.class);

	private int freeTimeSize = 0;

	private int busyTimeSize = 0;

	private ArrayList<TimePair> freeTimeList = new ArrayList<TimePair>();

	private volatile boolean isOpen;

	private HashMap<TaskHandleThread, Object> runningMap;

	private String poolName = "DefaultThreadPool";

	private ExecutorService executor;

	public PushingThreadPool() {
		runningMap = new HashMap<TaskHandleThread, Object>();
	}

	public void setFreeTime(String freeTime) throws Exception {
		String[] freeTimeArray = freeTime.split(";");
		for (int i = 0; i < freeTimeArray.length; i++) {
			String[] freeTimeStr = freeTimeArray[i].split("-");
			checkTime(freeTimeStr[0]);
			checkTime(freeTimeStr[1]);
			TimePair pair = new TimePair(freeTimeStr[0], freeTimeStr[1]);
			freeTimeList.add(pair);
		}
	}

	private void checkTime(String time) throws Exception {
		SimpleDateFormat format = new SimpleDateFormat("HH:mm");
		Date t = format.parse(time);
		String time2 = format.format(t);
		if (!time2.equals(time)) {
			throw new Exception("schedule incorrect, must be: HH:mm-HH:mm;...;HH:mm-HH:mm format." + time);
		}
	}

	private boolean isNowFreeTime() {
		Date t = new Date(System.currentTimeMillis());
		SimpleDateFormat format = new SimpleDateFormat("HH:mm");
		String now = format.format(t);
		for (int i = 0; i < freeTimeList.size(); i++) {
			TimePair pair = freeTimeList.get(i);
			if ((now.compareTo(pair.getBeginTime()) >= 0) && (now.compareTo(pair.getEndTime()) <= 0)) {
				return true;
			}
		}
		return false;
	}

	public void setFreeTimeSize(int freeTimeSize) {
		this.freeTimeSize = freeTimeSize;
	}

	public void setBusyTimeSize(int busyTimeSize) {
		this.busyTimeSize = busyTimeSize;
	}

	public boolean isAllThreadFree() {
		return 0 == runningMap.size();
	}

	public void setPoolName(String poolName) {
		this.poolName = poolName;
	}

	public void initialize() {
		executor = Executors.newFixedThreadPool(freeTimeSize);
		isOpen = true;
	}

	public void put(IThreadPoolTask task) throws InterruptedException, Exception {
		waitPut(task, 10);
	}

	public void waitPut(IThreadPoolTask task, long timeout) throws InterruptedException, Exception {
		if (!isOpen) {
			throw new Exception("Pool not initialized.");
		}
		int size = busyTimeSize;
		if (isNowFreeTime()) {
			size = freeTimeSize;
		}
		TaskHandleThread thread = new TaskHandleThread(task);
		synchronized (runningMap) {
			if (runningMap.size() < size) {
				executor.execute(thread);
				runningMap.put(thread, null);
				return;
			} else {
				try {
					runningMap.wait(timeout);
				} catch (InterruptedException e) {
				}
			}
		}
		if (!isOpen) {
			throw new Exception("Pool not initialized.");
		}
		if (isNowFreeTime()) {
			size = freeTimeSize;
		}
		synchronized (runningMap) {
			if (runningMap.size() < size) {
				executor.execute(thread);
				runningMap.put(thread, null);
				return;
			}
		}
		throw new InterruptedException("Timeout in getting free thread.");
	}

	public void terminate() {
		isOpen = false;
		try {
			executor.shutdown();
		} catch (Exception e) {
		}
		executor = null;
		int count = 0;
		while (!isAllThreadFree()) {
			try {
				Thread.sleep(100);
			} catch (Exception e) {
			}
			if (count++ >= 30) {
				log.info(poolName + " active thread info: " + getActiveTasksInfo());
				count = 0;
			}
		}
	}

	private void turnToFree(TaskHandleThread thread) {
		synchronized (runningMap) {
			runningMap.remove(thread);
			runningMap.notifyAll();
		}
	}

	class TaskHandleThread implements Runnable {
		private IThreadPoolTask threadTask = null;

		private String taskInfo;

		TaskHandleThread(IThreadPoolTask threadTask) {
			this.threadTask = threadTask;
			try {
				taskInfo = threadTask.getActiveInfo();
			} catch (Exception e) {
			}
		}

		public void run() {
			try {
				threadTask.doTask();
			} catch (Throwable t) {
				log.error("task execute failed: ", t);
			}
			turnToFree(this);
		}

		public String getTaskInfo() {
			return taskInfo;
		}
	}

	class TimePair {
		private String beginTime;

		private String endTime;

		TimePair(String beginTime, String endTime) {
			this.beginTime = beginTime;
			this.endTime = endTime;
		}

		public String getBeginTime() {
			return beginTime;
		}

		public String getEndTime() {
			return endTime;
		}
	}

	public String getActiveTasksInfo() {
		Iterator<TaskHandleThread> iterator = runningMap.keySet().iterator();
		StringBuffer sb = new StringBuffer();
		while (iterator.hasNext()) {
			TaskHandleThread thread = iterator.next();
			String info = thread.getTaskInfo();
			if (info != null) {
				sb.append(info);
				sb.append(", ");
			}
		}
		return sb.toString();
	}

	public int size() {
		if (isNowFreeTime()) {
			return freeTimeSize;
		} else {
			return busyTimeSize;
		}
	}

	public int getFreeTimeSize() {
		return freeTimeSize;
	}

	public int getBusyTimeSize() {
		return busyTimeSize;
	}

	public int getFreeThreadCount() {
		int count = size() - runningMap.size();
		return count > 0 ? count : 0;
	}

	public Future<?> submit(Callable<?> task) {
		Future<?> future = executor.submit(task);
		return future;
	}

}
